<?php

/**
 * NumberFormat class file.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the BSD License.
 *
 * Copyright(c) 2004 by Qiang Xue. All rights reserved.
 *
 * To contact the author write to {@link mailto:qiang.xue@gmail.com Qiang Xue}
 * The latest version of PRADO can be obtained from:
 * {@link http://prado.sourceforge.net/}
 *
 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
 * @version $Revision: 1.6 $  $Date: 2005/12/20 09:32:42 $
 * @package System.I18N.core
 */

/**
 * Get the NumberFormatInfo class file.
 */
require_once(dirname(__FILE__).'/NumberFormatInfo.php');


/**
 * Get the encoding utilities
 */
require_once(dirname(__FILE__).'/util.php');


/**
 * NumberFormat class.
 *
 * NumberFormat formats decimal numbers in any locale. The decimal
 * number is formatted according to a particular pattern. These
 * patterns can arise from the NumberFormatInfo object which is
 * culturally sensitive. The NumberFormat class can be instantiated in
 * many ways. E.g.
 *
 * <code>
 *  //create a invariant number formatter.
 *	$formatter = new NumberFormat();
 *
 *  //create a number format for the french language locale.
 *  $fr = new NumberFormat('fr');
 *
 *  //create a number format base on a NumberFormatInfo instance $numberInfo.
 *  $format = new NumberFormat($numberInfo);
 * </code>
 *
 * A normal decimal number can also be displayed as a currency
 * or as a percentage. For example
 * <code>
 * $format->format(1234.5); //Decimal number "1234.5"
 * $format->format(1234.5,'c'); //Default currency "$1234.50"
 * $format->format(0.25, 'p') //Percent "25%"
 * </code>
 *
 * Currency is formated using the localized currency pattern. For example
 * to format the number as Japanese Yen:
 * <code>
 *  $ja = new NumberFormat('ja_JP');
 *
 *  //Japanese currency pattern, and using Japanese Yen symbol
 *  $ja->format(123.14,'c','JPY'); //�?123 (Yen 123)
 * </code>
 * For each culture, the symbol for each currency may be different.
 *
 * @author Xiang Wei Zhuo <weizhuo[at]gmail[dot]com>
 * @version v1.0, last update on Fri Dec 10 18:10:20 EST 2004
 * @package System.I18N.core
 */
class NumberFormat
{

	/**
	 * The DateTimeFormatInfo, containing culture specific patterns and names.
 	 * @var DateTimeFormatInfo
	 */
	protected $formatInfo;

	/**
	 * Create a new number format instance. The constructor can be instantiated
	 * with a string that represent a culture/locale. Similarly, passing
	 * a CultureInfo or NumberFormatInfo instance will instantiated a instance
	 * for that particular culture.
	 * @param mixed either null, a CultureInfo, a NumberFormatInfo, or string
	 * @return NumberFormat
	 */
	function __construct($formatInfo=null)
	{
		if($formatInfo === null)
			$this->formatInfo = NumberFormatInfo::getInvariantInfo();
		else if($formatInfo instanceof CultureInfo)
			$this->formatInfo = $formatInfo->NumberFormat;
		else if($formatInfo instanceof NumberFormatInfo)
			$this->formatInfo = $formatInfo;
		else
			$this->formatInfo =
				NumberFormatInfo::getInstance($formatInfo);
	}

	/**
	 * For the number for a certain pattern. The valid patterns are
	 * 'c', 'd', 'e', 'p' or a custom pattern, such as "#.000" for
	 * 3 decimal places.
	 * @param mixed the number to format.
	 * @param string the format pattern, either, 'c', 'd', 'e', 'p'
	 * or a custom pattern. E.g. "#.000" will format the number to
	 * 3 decimal places.
	 * @param string 3-letter ISO 4217 code. For example, the code
	 * "USD" represents the US Dollar and "EUR" represents the Euro currency.
	 * @return string formatted number string
	 */
	function format($number, $pattern='d', $currency='USD', $charset='UTF-8')
	{
		$this->setPattern($pattern);

		if(strtolower($pattern) == 'p')
			$number = $number * 100;

		$string = (string)$number;

		$decimal = $this->formatDecimal($string);
		$integer = $this->formatInteger(abs($number));

		if(strlen($decimal)>0)
			$result = $integer.$decimal;
		else
			$result = $integer;

		//get the suffix
		if($number >= 0)
			$suffix = $this->formatInfo->PositivePattern;
		else if($number < 0)
			$suffix = $this->formatInfo->NegativePattern;
		else
			$suffix = array("","");

		//append and prepend suffix
		$result = $suffix[0].$result.$suffix[1];

		//replace currency sign
		$symbol = @$this->formatInfo->getCurrencySymbol($currency);
		if($symbol === null) {
			$symbol = $currency;
		}

		$result = str_replace('¤',$symbol, $result);

		return I18N_toEncoding($result, $charset);
	}

	/**
	 * For the integer, perform groupings and string padding.
	 * @param string the decimal number in string form.
	 * @return string  formatted integer string with grouping
	 */
	protected function formatInteger($string)
	{
		$string = (string)$string;

		$decimalDigits = $this->formatInfo->DecimalDigits;
		//if not decimal digits, assume 0 decimal points.
		if(is_int($decimalDigits) && $decimalDigits > 0)
			$string = (string)round(floatval($string),$decimalDigits);
		$dp = strpos($string, '.');
		if(is_int($dp))
			$string = substr($string, 0, $dp);
		$integer = '';

		$digitSize = $this->formatInfo->getDigitSize();

		$string = str_pad($string, $digitSize, '0',STR_PAD_LEFT);

		$len = strlen($string);

		$groupSeparator = $this->formatInfo->GroupSeparator;
		$groupSize = $this->formatInfo->GroupSizes;


		$firstGroup = true;
		$multiGroup = is_int($groupSize[1]);
		$count = 0;

		if(is_int($groupSize[0]))
		{
			//now for the integer groupings
			for($i=0; $i<$len; $i++)
			{
				$char = $string{$len-$i-1};

				if($multiGroup && $count == 0)
				{
					if($i != 0 && $i%$groupSize[0] == 0)
					{
						$integer = $groupSeparator . $integer;
						$count++;
					}
				}
				else if($multiGroup && $count >= 1)
				{
					if($i != 0 && ($i-$groupSize[0])%$groupSize[1] == 0)
					{
						$integer = $groupSeparator . $integer;
						$count++;
					}
				}
				else
				{
					if($i != 0 && $i%$groupSize[0] == 0)
					{
						$integer = $groupSeparator . $integer;
						$count++;
					}
				}

				$integer = $char . $integer;
			}
		}
		else
			$integer = $string;

		return $integer;
	}

	/**
	 * Format the decimal places.
	 * @param string the decimal number in string form.
	 * @return string formatted decimal places.
	 */
	protected function formatDecimal($string)
	{
		$dp = strpos($string, '.');
		$decimal = '';

		$decimalDigits = $this->formatInfo->DecimalDigits;
		$decimalSeparator = $this->formatInfo->DecimalSeparator;

		//do the correct rounding here
		//$string = round(floatval($string), $decimalDigits);
		if(is_int($dp))
		{
			if($decimalDigits == -1)
			{
				$decimal = substr($string, $dp+1);
			}
			else if(is_int($decimalDigits))
			{
				$float = round((float)$string, $decimalDigits);
				if(strpos((string)$float, '.') === false)
				{
					$decimal = str_pad($decimal,$decimalDigits,'0');
				}
				else
				{
					$decimal = substr($float, strpos($float,'.')+1);
					if(strlen($decimal)<$decimalDigits)
						$decimal = str_pad($decimal,$decimalDigits,'0');
				}
			}
			else
				return $decimal;

			return $decimalSeparator.$decimal;
		}
		else if ($decimalDigits > 0)
			return $decimalSeparator.str_pad($decimal,$decimalDigits,'0');

		return $decimal;
	}

	/**
	 * Set the pattern to format against. The default patterns
	 * are retrieved from the NumberFormatInfo instance.
	 * @param string the requested patterns.
	 * @return string a number format pattern.
	 */
	protected function setPattern($pattern)
	{
		switch($pattern)
		{
			case 'c':
			case 'C':
				$this->formatInfo->setPattern(NumberFormatInfo::CURRENCY);
				break;
			case 'd':
			case 'D':
				$this->formatInfo->setPattern(NumberFormatInfo::DECIMAL);
				break;
			case 'e':
			case 'E':
				$this->formatInfo->setPattern(NumberFormatInfo::SCIENTIFIC);
				break;
			case 'p':
			case 'P':
				$this->formatInfo->setPattern(NumberFormatInfo::PERCENTAGE);
				break;
			default:
				$this->formatInfo->setPattern($pattern);
				break;
		}
	}
}

